package com.talkweb.osmharmony.tileprovider.tilesource;

import com.talkweb.osmharmony.api.IMapView;
import com.talkweb.osmharmony.tileprovider.PixelMapPool;
import com.talkweb.osmharmony.tileprovider.ReusablePixelMapElement;
import com.talkweb.osmharmony.tileprovider.util.Counters;
import com.talkweb.osmharmony.util.Log;
import com.talkweb.osmharmony.util.MapTileIndex;
import ohos.agp.components.element.Element;
import ohos.media.image.ImageSource;
import ohos.media.image.PixelMap;
import ohos.media.image.common.ImageInfo;

import java.io.File;
import java.io.InputStream;
import java.util.Random;

public abstract class PixelMapTileSourceBase implements ITileSource {

    private static int globalOrdinal = 0;

    private final int mMinimumZoomLevel;
    private final int mMaximumZoomLevel;

    private final int mOrdinal;
    protected String mName;
    protected String mCopyright;
    protected final String mImageFilenameEnding;
    protected final Random random = new Random();

    private final int mTileSizePixels;

    //private final string mResourceId;

    /**
     * Constructor
     *
     * @param aName                a human-friendly name for this tile source. this name is also used on the file system, to keep the characters linux file system friendly
     * @param aZoomMinLevel        the minimum zoom level this tile source can provide
     * @param aZoomMaxLevel        the maximum zoom level this tile source can provide
     * @param aTileSizePixels      the tile size in pixels this tile source provides
     * @param aImageFilenameEnding the file name extension used when constructing the filename
     */
    public PixelMapTileSourceBase(final String aName,
                                  final int aZoomMinLevel, final int aZoomMaxLevel, final int aTileSizePixels,
                                  final String aImageFilenameEnding) {
        this(aName, aZoomMinLevel, aZoomMaxLevel, aTileSizePixels, aImageFilenameEnding, null);
    }

    /**
     * Constructor
     *
     * @param aName                a human-friendly name for this tile source. this name is also used on the file system, to keep the characters linux file system friendly
     * @param aZoomMinLevel        the minimum zoom level this tile source can provide
     * @param aZoomMaxLevel        the maximum zoom level this tile source can provide
     * @param aTileSizePixels      the tile size in pixels this tile source provides
     * @param aImageFilenameEnding the file name extension used when constructing the filename
     */
    public PixelMapTileSourceBase(final String aName,
                                  final int aZoomMinLevel, final int aZoomMaxLevel, final int aTileSizePixels,
                                  final String aImageFilenameEnding, final String aCopyrightNotice) {
        mOrdinal = globalOrdinal++;
        mName = aName;
        mMinimumZoomLevel = aZoomMinLevel;
        mMaximumZoomLevel = aZoomMaxLevel;
        mTileSizePixels = aTileSizePixels;
        mImageFilenameEnding = aImageFilenameEnding;
        mCopyright = aCopyrightNotice;
    }


    @Override
    public int ordinal() {
        return mOrdinal;
    }

    @Override
    public String name() {
        return mName;
    }

    public String pathBase() {
        return mName;
    }

    public String imageFilenameEnding() {
        return mImageFilenameEnding;
    }

    @Override
    public int getMinimumZoomLevel() {
        return mMinimumZoomLevel;
    }

    @Override
    public int getMaximumZoomLevel() {
        return mMaximumZoomLevel;
    }

    @Override
    public int getTileSizePixels() {
        return mTileSizePixels;
    }

    @Override
    public String toString() {
        return name();
    }

    @Override
    public Element getDrawable(final String aFilePath) throws LowMemoryException {
        //Log.d(IMapView.LOGTAG, aFilePath + " attempting to load bitmap");
        try {
            // We need to determine the real tile size first..
            // Otherwise, if mTileSizePixel is not correct, we will never be able to reuse bitmaps
            // from the pool, as we request them with mTileSizePixels, while they are stored with
            // their real size
            // default implementation will load the file as a bitmap and create
            // a BitmapDrawable from it

            ImageSource.SourceOptions sourceOptions = new ImageSource.SourceOptions();
            ImageSource imageSource = ImageSource.create(aFilePath, sourceOptions);
            ImageInfo imageInfo = imageSource.getImageInfo();
            final int realSize = imageInfo != null && imageInfo.size != null ? imageInfo.size.height : 0;

            ImageSource.DecodingOptions decodingOptions = new ImageSource.DecodingOptions();
            PixelMapPool.getInstance().applyReusableOptions(decodingOptions, realSize, realSize);
            PixelMap pixelMap = imageSource.createPixelmap(decodingOptions);

            if(!imageSource.isReleased()){
                imageSource.release();
            }

            if (pixelMap != null) {
                return new ReusablePixelMapElement(pixelMap);
            } else {
                File bmp = new File(aFilePath);
                if (bmp.exists()) {
                    // if we couldn't load it then it's invalid - delete it
                    Log.d(IMapView.LOGTAG, aFilePath + " is an invalid image file, deleting...");
                    try {
                        new File(aFilePath).delete();
                    } catch (final Throwable e) {
                        Log.e(IMapView.LOGTAG, "Error deleting invalid file: " + aFilePath, e);
                    }
                } else {
                    Log.d(IMapView.LOGTAG, "Request tile: " + aFilePath + " does not exist");
                }
            }
        } catch (final OutOfMemoryError e) {
            Log.e(IMapView.LOGTAG, "OutOfMemoryError loading bitmap: " + aFilePath);
            System.gc();
            throw new LowMemoryException(e);
        } catch (final Exception e) {
            Log.e(IMapView.LOGTAG, "Unexpected error loading bitmap: " + aFilePath, e);
            Counters.tileDownloadErrors++;
            System.gc();
        }
        return null;
    }

    @Override
    public String getTileRelativeFilenameString(final long pMapTileIndex) {
        final StringBuilder sb = new StringBuilder();
        sb.append(pathBase());
        sb.append('/');
        sb.append(MapTileIndex.getZoom(pMapTileIndex));
        sb.append('/');
        sb.append(MapTileIndex.getX(pMapTileIndex));
        sb.append('/');
        sb.append(MapTileIndex.getY(pMapTileIndex));
        sb.append(imageFilenameEnding());
        return sb.toString();
    }

    @Override
    public Element getDrawable(final InputStream aFileInputStream) throws LowMemoryException {
        try {
            // default implementation will load the file as a bitmap and create
            // a BitmapDrawable from it
            ImageSource.SourceOptions sourceOptions = new ImageSource.SourceOptions();
            ImageSource imageSource = ImageSource.create(aFileInputStream, sourceOptions);

            // We need to determine the real tile size first..
            // Otherwise, if mTileSizePixel is not correct, we will never be able to reuse bitmaps
            // from the pool, as we request them with mTileSizePixels, while they are stored with
            // their real size
            ImageInfo imageInfo = imageSource.getImageInfo();
            int realSize = imageInfo != null && imageInfo.size != null ? imageInfo.size.height : mTileSizePixels;

            ImageSource.DecodingOptions decodingOptions = new ImageSource.DecodingOptions();
            PixelMapPool.getInstance().applyReusableOptions(decodingOptions, realSize, realSize);
            PixelMap pixelMap = imageSource.createPixelmap(decodingOptions);

            if(!imageSource.isReleased()){
                imageSource.release();
            }

            if (pixelMap != null) {
                return new ReusablePixelMapElement(pixelMap);
            }
        } catch (final OutOfMemoryError e) {
            Log.e(IMapView.LOGTAG, "OutOfMemoryError loading bitmap");
            System.gc();
            throw new LowMemoryException(e);
        } catch (Exception ex) {
            Log.w(IMapView.LOGTAG, "#547 Error loading bitmap" + pathBase(), ex);
        }
        return null;
    }

    public static final class LowMemoryException extends Exception {
        private static final long serialVersionUID = 146526524087765134L;

        public LowMemoryException(final String pDetailMessage) {
            super(pDetailMessage);
        }

        public LowMemoryException(final Throwable pThrowable) {
            super(pThrowable);
        }
    }

    @Override
    public String getCopyrightNotice() {
        return mCopyright;
    }
}
